package com.chen.mkarm

import android.content.Context
import android.hardware.usb.UsbManager
import android.util.Log
import com.hoho.android.usbserial.driver.*
import com.hoho.android.usbserial.util.*
import kotlinx.coroutines.*
import java.util.Locale
import java.util.concurrent.Executor
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue
import android.os.Build
import android.app.PendingIntent
import android.content.Intent


data class UsbSerialParams(
    var baudRate: Int, //比如 115200
    var dataBits: Int, //比如 8
    var stopBits: Int, //比如 UsbSerialPort.STOPBITS_1
    var parity: Int, //比如 UsbSerialPort.PARITY_NONE
    var debug: Boolean
)

object HexUtil {
    fun hexStringToBytes(hexString: String?): ByteArray? {
        var hexString = hexString
        if (hexString == null || hexString == "") {
            return null
        }
        hexString = hexString.uppercase(Locale.getDefault())
        val length = hexString.length / 2
        val hexChars = hexString.toCharArray()
        val d = ByteArray(length)
        for (i in 0..<length) {
            val pos = i * 2
            d[i] =
                (charToByte(hexChars[pos]).toInt() shl 4 or charToByte(hexChars[pos + 1]).toInt()).toByte()
        }
        return d
    }

    private fun charToByte(c: Char): Byte {
        return "0123456789ABCDEF".indexOf(c).toByte()
    }

    fun bytesToHexString(b: ByteArray): String? {
        if (b.size == 0) {
            return null
        }
        val sb = StringBuilder("")
        for (i in b.indices) {
            val value = b[i].toInt() and 0xFF
            val hv = Integer.toHexString(value)
            if (hv.length < 2) {
                sb.append(0)
            }

            sb.append("0x").append(hv).append(" ")
        }
        return sb.toString()
    }

    fun toByteString(b: Byte): String {
        var hex = Integer.toHexString(b.toInt() and 0xFF)
        if (hex.length == 1) {
            hex = '0'.toString() + hex
        }
        return hex.uppercase(Locale.getDefault())
    }
}

class UsbSerialManager(
    private val context: Context,
    private val params: UsbSerialParams,
    private val receiver: (String) -> Unit
) :
    SerialInputOutputManager.Listener {
    private var port: UsbSerialPort? = null
    private lateinit var usbIoManager: SerialInputOutputManager
    private val dataQueue = LinkedBlockingQueue<Byte>()
    private var cmd: Cmd = Cmd()
    private val singleExecutor: Executor by lazy {
        Executors.newSingleThreadExecutor()
    }
    private val readRunnable: Runnable
    private val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager

    val PREAMBLE : Byte = 'A'.code.toByte()

    init {
        readRunnable = Runnable {
            while (port?.isOpen == true || dataQueue.isNotEmpty()) {
                val byte = dataQueue.take()
                if (byte == 'A'.code.toByte()) { //前导帧
                    cmd = Cmd()
                    cmd.preamble = PREAMBLE
                    log("前导帧:0x${HexUtil.toByteString(PREAMBLE)}")
                    cmd.length = dataQueue.take()
                    log("长度:${cmd.length}")
                    readPayload(dataQueue)
                    log("payload:${HexUtil.bytesToHexString(cmd.payload.toByteArray())}")
                    val checkSum = dataQueue.take()
                    cmd.checkSum = checkSum
                    log("校验:0x${HexUtil.toByteString(checkSum)}")
                    receiver.invoke(String(cmd.payload.toByteArray()))
                    cmd.clear()
                } else {
                    Log.e("Heiko", "被抛弃:0x${HexUtil.toByteString(byte)}")
                }
            }
        }
    }

    private fun readPayload(dataStack: LinkedBlockingQueue<Byte>) {
        for (i in 0 until cmd.length) {
            cmd.payload.add(dataStack.take())
        }
    }

    fun requestPermission() {
        val driver = getDriver() ?: return
        val flags =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0
        val permissionIntent = PendingIntent.getBroadcast(
            context,
            0,
            Intent("com.android.example.USB_PERMISSION"),
            flags
        )
        usbManager.requestPermission(driver.device, permissionIntent)
    }

    private fun getDrivers(): MutableList<UsbSerialDriver> {
        return UsbSerialProber.getDefaultProber().findAllDrivers(usbManager)
    }

    private fun getDriver(): UsbSerialDriver? {
        val availableDrivers = getDrivers()
        if (availableDrivers.isEmpty()) {
            log("availableDrivers is empty.")
            return null
        }

        return availableDrivers[0]
    }

    fun hasPermission(): Boolean {
        val driver = getDriver() ?: return false
        return usbManager.hasPermission(driver.device)
    }

    fun openDevice() {
        if (port?.isOpen == true) {
            log("port is opened.")
            return
        }
        val driver = getDriver() ?: return
        debugLogDrivers()
        val connection = usbManager.openDevice(driver.device) ?: return
        log("connection:$connection")

        port = driver.ports[0] // Most devices have just one port (port 0)
        port?.open(connection)
        port?.setParameters(params.baudRate, params.dataBits, params.stopBits, params.parity)

        usbIoManager = SerialInputOutputManager(port, this)
        usbIoManager.start()
        singleExecutor.execute(readRunnable)

        log("usbIoManager.start")
    }

    private fun debugLogDrivers() {
        if (params.debug) {
            getDrivers().forEach {
                val device = it.device
                log(
                    "deviceId:${device.deviceId} " +
                            " deviceName:${device.deviceName} " +
                            " deviceProtocol:${device.deviceProtocol} " +
                            " productName:${device.productName}" +
                            " productId:${device.productId}" +
                            " manufacturerName:${device.manufacturerName}" +
                            " configurationCount:${device.configurationCount}" +
                            " serialNumber:${device.serialNumber}" +
                            " vendorId:${device.vendorId}"
                )
            }
        }
    }

    fun closeDevice() {
        port?.close()
        port = null
    }

    private fun receive(data: ByteArray?) {
        log("receive:${HexDump.dumpHexString(data)}", "RRRRRRR")

        if (data == null) return
        for (byte in data) {
            dataQueue.put(byte)
        }
    }

    override fun onNewData(data: ByteArray?) {
        receive(data)
    }

    override fun onRunError(e: Exception?) {
        log("onRunError:${e?.message}")
    }

    private fun log(message: String, tag: String = "Heiko") {
        Log.i(tag, message)
    }

    public fun sendData(data: String) {
        port?.write(data.toByteArray(), 1000)
    }
}

class Cmd {
    companion object {
        const val PREAMBLE: Byte = 0xAA.toByte()
    }

    var preamble: Byte? = null
    var length: Byte = -1
    var payload = ArrayList<Byte>()
    var checkSum: Byte? = null

    fun clear() {
        preamble = null
        length = -1
        payload.clear()
        checkSum = null
    }
}